﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DynamicProperties
{
	/// <summary>
	/// Class used to store subscribers for events on specific object instance.
	/// </summary>
	internal class EventSubscribers
	{
		/// <summary>
		/// DynamicProperty used to store event subscribers for other properties changes.
		/// </summary>
		internal static readonly DynamicProperty<object, EventSubscribers> EventSubscribersProperty
			= new DynamicProperty<object, EventSubscribers>("EventSubscribers", () => new EventSubscribers());

		private object syncLock = new object();
		Dictionary<int, object> delegates;
		
		public EventSubscribers()
		{
			this.delegates =new Dictionary<int,object>();
		}

		internal bool GetHasAnySubscribers(DynamicProperty property)
		{
			bool result = this.delegates.ContainsKey(property.UniqueInstanceNumber);
			return result;
		}

		internal void InvokeEvent<TOwner, TProperty>(TOwner target, DynamicPropertyChangedParams<TOwner, TProperty> args)
			where TOwner : class
		{
			// validate
			if(target == null)
				throw new ArgumentNullException("target");

			if(args == null)
				throw new ArgumentNullException("args");

			DynamicPropertyChangedCallback<TOwner,TProperty> subscribers = null;

			lock(syncLock)
			{
				// has this property subscribers?
				object subscribersBoxed;
				bool found = delegates.TryGetValue(args.Property.UniqueInstanceNumber, out subscribersBoxed);

				if (!found)
					return;

				// cast to generic type
				subscribers = (DynamicPropertyChangedCallback<TOwner, TProperty>)subscribersBoxed;
			}

			if(subscribers != null)
			{
				// invoke event
				subscribers.Invoke(target, args);
			}
		}

		internal void RegisterSubscriber<TOwner, TProperty>(DynamicProperty<TOwner,TProperty> property, DynamicPropertyChangedCallback<TOwner,TProperty> callback)
			where TOwner : class
		{
			// validate
			if(property == null)
				throw new ArgumentNullException("property");

			if(callback == null)
				throw new ArgumentNullException("callback");

			lock (syncLock)
			{
				// has this property subscribers?
				object subscribersBoxed;
				bool found = delegates.TryGetValue(property.UniqueInstanceNumber, out subscribersBoxed);

				if (!found)
				{
					// there is no callback - add new callback to delegates list
					delegates.Add(property.UniqueInstanceNumber, callback);
				}
				else
				{
					// cast to generic type
					var subscribers = (DynamicPropertyChangedCallback<TOwner, TProperty>)subscribersBoxed;

					// add callback to existing callback
					subscribers += callback;

					// new 'subscribers' should be added back to delegates dictionary
					delegates[property.UniqueInstanceNumber] = subscribers;
				}
			}
		}

		internal void UnregisterSubscriber<TOwner, TProperty>(DynamicProperty<TOwner, TProperty> property, DynamicPropertyChangedCallback<TOwner, TProperty> callback)
			where TOwner : class
		{
			// validate
			if (property == null)
				throw new ArgumentNullException("property");

			if (callback == null)
				throw new ArgumentNullException("callback");

			lock (syncLock)
			{
				// has this property subscribers?
				object subscribersBoxed;
				bool found = delegates.TryGetValue(property.UniqueInstanceNumber, out subscribersBoxed);

				if (!found)
				{
					// there is no callback - cannot unregister
					return;
				}

				// cast to generic type
				var subscribers = (DynamicPropertyChangedCallback<TOwner, TProperty>)subscribersBoxed;

				// remove callback from existing callback
				subscribers -= callback;

				// was it last callback? remove item form dictionary of delegates
				if (subscribers == null)
				{
					delegates.Remove(property.UniqueInstanceNumber);
				}
				else
				{
					// if not last, update changed object back to delegates dictionary
					delegates[property.UniqueInstanceNumber] = subscribers;
				}
			}
		}
	}
}
